
// Copyright (C) 2011 Luca Piccioni
// 
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//   
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Text;

using log4net;
using log4net.Appender;
using log4net.Config;
using log4net.Core;
using log4net.Repository;
using log4net.Repository.Hierarchy;

namespace Derm
{
	/// <summary>
	/// Generic interface log logging.
	/// </summary>
	public interface ILogger
	{
		/// <summary>
		/// Log a message object with the log4net.Core.Level.Verbose level.
		/// </summary>
		/// <param name="message">The message object to log</param>
		/// <param name="exception">The exception to log</param>
		void Verbose(string message, Exception exception);

		/// <summary>
		/// Log a message object with the log4net.Core.Level.Verbose level.
		/// </summary>
		/// <param name="format">A String containing zero or more format items.</param>
		/// <param name="args">An Object array containing zero or more objects to format</param>
		void Verbose(string format, params object[] args);

		/// <summary>
		/// Log a message object with the log4net.Core.Level.Debug level.
		/// </summary>
		/// <param name="message">The message object to log</param>
		/// <param name="exception">The exception to log</param>
		void Debug(string message, Exception exception);

		/// <summary>
		/// Log a message object with the log4net.Core.Level.Debug level.
		/// </summary>
		/// <param name="format">A String containing zero or more format items.</param>
		/// <param name="args">An Object array containing zero or more objects to format</param>
		void Debug(string format, params object[] args);

		/// <summary>
		/// Log a message object with the log4net.Core.Level.Info level.
		/// </summary>
		/// <param name="message">The message object to log</param>
		/// <param name="exception">The exception to log</param>
		void Info(string message, Exception exception);

		/// <summary>
		/// Log a message object with the log4net.Core.Level.Info level.
		/// </summary>
		/// <param name="format">A String containing zero or more format items.</param>
		/// <param name="args">An Object array containing zero or more objects to format</param>
		void Info(string format, params object[] args);

		/// <summary>
		/// Log a message object with the log4net.Core.Level.Warn level.
		/// </summary>
		/// <param name="message">The message object to log</param>
		/// <param name="exception">The exception to log</param>
		void Warn(string message, Exception exception);

		/// <summary>
		/// Log a message object with the log4net.Core.Level.Warn level.
		/// </summary>
		/// <param name="format">A String containing zero or more format items.</param>
		/// <param name="args">An Object array containing zero or more objects to format</param>
		void Warn(string format, params object[] args);

		/// <summary>
		/// Log a message object with the log4net.Core.Level.Error level.
		/// </summary>
		/// <param name="message">The message object to log</param>
		/// <param name="exception">The exception to log</param>
		void Error(string message, Exception exception);

		/// <summary>
		/// Log a message object with the log4net.Core.Level.Error level.
		/// </summary>
		/// <param name="format">A String containing zero or more format items.</param>
		/// <param name="args">An Object array containing zero or more objects to format</param>
		void Error(string format, params object[] args);

		/// <summary>
		/// Log a message object with the log4net.Core.Level.Fatal level.
		/// </summary>
		/// <param name="message">The message object to log</param>
		/// <param name="exception">The exception to log</param>
		void Fatal(string message, Exception exception);

		/// <summary>
		/// Log a message object with the log4net.Core.Level.Fatal level.
		/// </summary>
		/// <param name="format">A String containing zero or more format items.</param>
		/// <param name="args">An Object array containing zero or more objects to format</param>
		void Fatal(string format, params object[] args);
	}

	/// <summary>
	/// Default logger implementation.
	/// </summary>
	class Logger : ILogger
	{
		#region Constructors

		public Logger(Type type)
		{
			if (type == null)
				throw new ArgumentNullException("type");

			// Store declaring type
			mDeclaringType = type;
			// Create logger
			mLogger = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
		}

		#endregion

		#region Logging

		/// <summary>
		/// Logging declaring type.
		/// </summary>
		private readonly Type mDeclaringType;

		/// <summary>
		/// Logger for declaring type.
		/// </summary>
		private readonly ILog mLogger;

		#endregion

		#region ILogger Implementation

		public void Verbose(string message, Exception exception)
		{
			mLogger.Logger.Log(mDeclaringType, Level.Verbose, message, exception);
		}

		public void Verbose(string format, params object[] args)
		{
			Verbose(String.Format(format, args), (Exception)null);
		}

		public void Debug(string message, Exception exception)
		{
			mLogger.Logger.Log(mDeclaringType, Level.Debug, message, exception);
		}

		public void Debug(string format, params object[] args)
		{
			Debug(String.Format(format, args), (Exception)null);
		}

		public void Info(string message, Exception exception)
		{
			mLogger.Logger.Log(mDeclaringType, Level.Info, message, exception);
		}

		public void Info(string format, params object[] args)
		{
			Info(String.Format(format, args), (Exception)null);
		}

		public void Warn(string message, Exception exception)
		{
			mLogger.Logger.Log(mDeclaringType, Level.Warn, message, exception);
		}

		public void Warn(string format, params object[] args)
		{
			Warn(String.Format(format, args), (Exception)null);
		}

		public void Error(string message, Exception exception)
		{
			mLogger.Logger.Log(mDeclaringType, Level.Error, message, exception);
		}

		public void Error(string format, params object[] args)
		{
			Error(String.Format(format, args), (Exception)null);
		}

		public void Fatal(string message, Exception exception)
		{
			mLogger.Logger.Log(mDeclaringType, Level.Fatal, message, exception);
		}

		public void Fatal(string format, params object[] args)
		{
			Fatal(String.Format(format, args), (Exception)null);
		}

		#endregion
	}

	/// <summary>
	/// Main class for controlling logging.
	/// </summary>
	public class Log
	{
		#region Log Verbosity

		/// <summary>
		/// Log verbosity level.
		/// </summary>
		public enum Verbosity : int
		{
			/// <summary>
			/// No log.
			/// </summary>
			Off = 0,
			/// <summary>
			/// Log fatal errors.
			/// </summary>
			Fatal,
			/// <summary>
			/// Log errors or more important events.
			/// </summary>
			Error,
			/// <summary>
			/// Log warnings or more important events.
			/// </summary>
			Warn,
			/// <summary>
			/// Log information or more important events.
			/// </summary>
			Info,
			/// <summary>
			/// Log debug information or more important events.
			/// </summary>
			Debug,
			/// <summary>
			/// Log everything.
			/// </summary>
			Verbose
		}

		/// <summary>
		/// Convert a <see cref="log4net.Core.Level"/> in <see cref="Verbosity"/>.
		/// </summary>
		/// <param name="level">
		/// A <see cref="log4net.Core.Level"/> to be converted into <see cref="Verbosity"/>.
		/// </param>
		/// <returns>
		/// It returns a <see cref="Verbosity"/> corresponding to <paramref name="level"/>.
		/// </returns>
		public static Verbosity Convert(Level level)
		{
			if (level <= Level.Verbose)
				return (Verbosity.Verbose);
			else if (level <= Level.Debug)
				return (Verbosity.Debug);
			else if (level <= Level.Info)
				return (Verbosity.Info);
			else if (level <= Level.Warn)
				return (Verbosity.Warn);
			else if (level <= Level.Error)
				return (Verbosity.Error);
			else if (level <= Level.Fatal)
				return (Verbosity.Fatal);
			else
				return (Verbosity.Off);
		}

		/// <summary>
		/// Convert a <see cref="Verbosity"/> in <see cref="log4net.Core.Level"/>.
		/// </summary>
		/// <param name="verbosity">
		/// A <see cref="Verbosity"/> to be converted into <see cref="log4net.Core.Level"/>.
		/// </param>
		/// <returns>
		/// It returns a <see cref="log4net.Core.Level"/> corresponding to <paramref name="verbosity"/>.
		/// </returns>
		public static log4net.Core.Level Convert(Verbosity verbosity)
		{
			switch (verbosity) {
				case Verbosity.Off:
					return (Level.Off);
				case Verbosity.Verbose:
					return (Level.Verbose);
				case Verbosity.Debug:
					return (Level.Debug);
				case Verbosity.Info:
					return (Level.Info);
				case Verbosity.Warn:
					return (Level.Warn);
				case Verbosity.Error:
					return (Level.Error);
				case Verbosity.Fatal:
					return (Level.Fatal);
				default:
					return (Level.Off);
			}
		}

		/// <summary>
		/// Set the overall log verbosity.
		/// </summary>
		/// <param name="verbLevel"></param>
		public static void SetLogVerbosity(Verbosity verbLevel)
		{
			ILoggerRepository[] repositories= LogManager.GetAllRepositories();

			foreach (ILoggerRepository repository in repositories) {
				log4net.Core.ILogger[] loggers = ((Hierarchy)repository).GetCurrentLoggers();
				Level logLevel = Level.All;

				switch (verbLevel) {
					case Verbosity.Off:
						logLevel = Level.Off;
						break;
					case Verbosity.Verbose:
						logLevel = Level.Verbose;
						break;
					case Verbosity.Debug:
						logLevel = Level.Debug;
						break;
					case Verbosity.Info:
						logLevel = Level.Info;
						break;
					case Verbosity.Warn:
						logLevel = Level.Warn;
						break;
					case Verbosity.Error:
						logLevel = Level.Error;
						break;
					case Verbosity.Fatal:
						logLevel = Level.Fatal;
						break;
				}

				// Set repository threshold level
				repository.Threshold = repository.LevelMap.LookupWithDefault(logLevel);
				// Set repository liggers threshold level
				foreach (log4net.Core.ILogger logger in loggers)
					((log4net.Repository.Hierarchy.Logger)logger).Level = ((Hierarchy)repository).LevelMap.LookupWithDefault(logLevel);
			}
		}

		#endregion

		#region Logger Configuration

		/// <summary>
		/// Configure the loggin system.
		/// </summary>
		/// <param name="path">
		/// A <see cref="System.String"/> that specify the log4net configuration file.
		/// </param>
		public static void Configure(string path)
		{
#if DEBUG
			XmlConfigurator.ConfigureAndWatch(new FileInfo(path));
#else
			XmlConfigurator.Configure(new FileInfo(path));
#endif
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="path"></param>
		public static void ConfigureLogFile(string path)
		{
			RollingFileAppender rFileAppender = (RollingFileAppender)Array.Find<IAppender>(LogManager.GetRepository().GetAppenders(), delegate(IAppender appender) {
				return ((appender.Name == "RollingFileAppender") && (appender is RollingFileAppender));
			});
			
			if (rFileAppender != null) {
				// Store log file path
				rFileAppender.File = path;
				rFileAppender.ActivateOptions();
			}
		}

		/// <summary>
		/// Create a logger for the specified type.
		/// </summary>
		/// <param name="type"></param>
		/// <returns></returns>
		public static ILogger GetLogger(Type type) { return (new Logger(type)); }

		#endregion
	}
}
